/*
 * Copyright 2011 Marcin Blazejewski
 *
 * This file is part of Ground0.
 *
 * Ground0 is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Ground0 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Ground0. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <config.h>
#include "camera.hpp"
#include "world.hpp"

#include <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <SDL/SDL.h>

/******************************************************************************/
/* camera */
/******************************************************************************/

Camera::Camera() : pos(0, 0, -10), focus(0,0,0), up(0,1,0) {
    // nop
}

Camera::Camera(Vector _pos, Vector _focus) : pos(_pos), focus(_focus), 
                                              up(0,1,0) {
    // nop
}

Camera::Camera(const Camera &prev) {
    pos = prev.pos;
    focus = prev.focus;
    up = prev.up;
    move_to = prev.move_to;
}


Camera::~Camera() {
    // nop
}

void Camera::set_pos(Vector &v) {
    pos = v;
}

void Camera::set_focus(Vector &v) {
    focus = v;
}

void Camera::apply() {
    gluLookAt(pos.x, pos.y, pos.z,
              focus.x, focus.y, focus.z,
              up.x, up.y, up.z);
}

void Camera::tick(unsigned int ms) {
    // nop
}

/******************************************************************************/
/* ChaseCam */
/******************************************************************************/

ChaseCam::ChaseCam(const Camera &prev, Sim *object) : 
    Camera(prev), target(object) {

}

ChaseCam::ChaseCam(Sim *object) {
    focus = object->pos;
    target = object;
}

void ChaseCam::set_chase_obj(Sim *s) {
    target = s;
}

void ChaseCam::tick(unsigned int ms) {
    focus = target->pos;
    move_to = focus;

    // camera up vec = focus obj up vec
    up = target->up;

    // dp - destination camera position
    // look in the direction the target obj is going (along its velocity vector)
    Vector dp( target->velocity );

    // if focus not moving, look where its facing
    if (dp.length() == 0) 
        dp = target->front;
    else 
        dp.normalize();

    
    // set nice looking view relative to target obj
    dp *= -4;  // move the camera back along ship's velocity axis
    dp += up;  // move the camera up
    dp *= 4;   // move away a bit more
    move_to += dp;
    
    // graudally move the camera to its target position
    Vector dc = move_to - pos;
    float l = dc.length();
        
    // crude way to get semi-smooth camera movement
    // move the camera faster, if its far away from its target pos
    if (l > 200) dc /= 2;
    else if (l > 100) dc /= 4;
    else if (l > 50) dc /= 6;
    else dc /= 8;
    pos += dc;
}


/******************************************************************************/
/* RearCam */
/******************************************************************************/

RearCam::RearCam(const Camera &prev, Stuff *object) : 
    Camera(prev), target(object) {

    // nop
}

RearCam::RearCam(Stuff *object) {
    focus = object->pos;
    target = object;
}

void RearCam::set_target_obj(Stuff *s) {
    target = s;
}

void RearCam::tick(unsigned int ms) {
    focus = target->pos;
    move_to = focus;

    // camera up vec = focus obj up vec
    up = target->up;
    // dp - destination camera position
    // look in the direction the target is facing (along its 'front' vector)
    Vector dp( target->front );

    dp = target->front;
    dp *= -16;  // move the camera back along ship's front axis
    move_to += dp;
    
    // graudally move the camera to its target position
    Vector dc = move_to - pos;
    float l = dc.length();

    // crude way to get semi-smooth camera movement
    // move the camera faster, if its far away from its target pos
    if (l > 50) dc *= 0.8;
    else dc *= 0.7;
    pos += dc;
}

/******************************************************************************/
/* TopCam */
/******************************************************************************/

TopCam::TopCam(const Camera &prev, Stuff *object) : 
    Camera(prev), target(object) {
    // nop
}

TopCam::TopCam(Stuff *object) {
    focus = object->pos;
    target = object;
}

void TopCam::set_target_obj(Stuff *s) {
    target = s;
}

void TopCam::tick(unsigned int ms) {
    focus = target->pos;
    move_to = focus;

    // camera up vec = focus obj up vec
    up = target->up;

    // look at the target from above
    Vector dp(target->front);
    
    dp *= 16;
    move_to += dp;
    
    // graudally move the camera to its target position
    Vector dc = move_to - pos;
    float l = dc.length();

    // crude way to get semi-smooth camera movement
    // move the camera faster, if its far away from its target pos
    if (l > 50) dc *= 0.8;
    else dc *= 0.7;
    pos += dc;
}


/******************************************************************************/
/* FixedCam */
/******************************************************************************/

FixedCam::FixedCam(const Camera &prev, Stuff *object) : 
    Camera(prev), target(object) {

    // nop
}

FixedCam::FixedCam(Stuff *object) {
    focus = object->pos;
    target = object;
}

void FixedCam::set_target_obj(Stuff *s) {
    target = s;
}

void FixedCam::tick(unsigned int ms) {
    focus = target->pos;
    move_to = focus;
}

/******************************************************************************/
/* FixedCam */
/******************************************************************************/

OrbitingCam::OrbitingCam(const Vector &_target,
                         const float r, const float e, 
                         const float h)  
    : target(_target) {

    focus = target;

    edit = false;
    distance = r;
    heading = h;
    elevation = e;
    update();
}

void OrbitingCam::tick(unsigned int ms) {
    if (edit) {
        SDL_GetRelativeMouseState(&x, &y);
        heading += x * M_PI / 270.0;
        elevation -= y * M_PI / 270.0;
    }
    update();
}

void OrbitingCam::update() {
    pos.set_polar(distance, heading, elevation);
    pos += target;
}

